package fr.babos.graphic.components.base 
{
	import flash.display.DisplayObjectContainer;
	import flash.events.Event;
	import flash.geom.Matrix;
	import flash.geom.Rectangle;
	import org.osflash.signals.Signal;
	
	/**
	 * Composant de base avec gestion des dimensions.
	 * @author ZoulouX
	 */
	public class ResizableComponent extends BaseComponent
	{
		/**
		 * La largeur du composant
		 */
		protected var _localWidth				:Number						= NaN;
		
		/**
		 * La hauteur du composant
		 */
		protected var _localHeight				:Number						= NaN;
		
		/**
		 * Le signal du redimensionnement
		 */
		protected var _onResized				:Signal						= new Signal();
		
		/**
		 * Le parent écouté
		 */
		protected var _watchedParent			:ResizableComponent;
		
		/**
		 * Position absolue par rapport au haut
		 */
		protected var _top						:Number;
		
		/**
		 * Position absolue par rapport à la droite
		 */
		protected var _right					:Number;
		
		/**
		 * Position absolue par rapport au bas
		 */
		protected var _bottom					:Number;
		
		/**
		 * Position absolue par rapport à la gauche
		 */
		protected var _left						:Number;
		
		/**
		 * Centrage horizontal du composant
		 */
		protected var _horizontalCenter			:Number;
		
		/**
		 * Centrage vertical du composant
		 */
		protected var _verticalCenter			:Number;
		
		/**
		 * Le décalage de placement horizontal
		 */
		protected var _horizontalOffset			:Number						= 0;
		
		/**
		 * Le décalage de placement vertical
		 */
		protected var _verticalOffset			:Number						= 0;
		
		/**
		 * La marge du haut
		 */
		protected var _topMargin				:int;
		
		/**
		 * La marge de droite
		 */
		protected var _rightMargin				:int;
		
		/**
		 * La marge du bas
		 */
		protected var _bottomMargin				:int;
		
		/**
		 * La marge de gauche
		 */
		protected var _leftMargin				:int;
		
		/**
		 * Masquer le contenu
		 */
		protected var _maskContent				:Boolean					= false;
		
		/**
		 * Si on autorise la suppression de cet élément
		 */
		protected var _allowRemove				:Boolean					= true;
		
		
		/**
		 * La largeur en passant par la superclasse
		 */
		public function get superWidth ():Number { return super.width; };
		public function set superWidth (value:Number):void
		{
			super.width = value;
		}
		
		/**
		 * La hauter en passant par la superclasse
		 */
		public function get superHeight ():Number { return super.height; };
		public function set superHeight (value:Number):void
		{
			super.height = value;
		}
		
		
		/**
		 * La largeur du composant
		 */
		override public function get width ():Number { return _localWidth; }
		override public function set width (value:Number):void
		{
			// Si la valeur est différente et définie
			if ((value <= 0 || value > 0) && value != _localWidth)
			{
				// Enregistrer la nouvelle largeur
				_localWidth = value;
				
				// Actualiser la position
				needReplace(true);
			}
		}
		
		/**
		 * La hauteur du composant
		 */
		override public function get height ():Number { return _localHeight; }
		override public function set height (value:Number):void
		{
			// Si la valeur est différente et définie
			if ((value <= 0 || value > 0) && value != _localHeight)
			{
				// Enregistrer la nouvelle largeur
				_localHeight = value;
				
				// Actualiser la position
				needReplace(true);
			}
		}
		
		/**
		 * La largeur totale du composant (avec les marges)
		 */
		public function get totalWidth ():Number { return _leftMargin + _localWidth + _rightMargin };
		
		/**
		 * La hauteur totale du composant (avec les marges)
		 */
		public function get totalHeight ():Number { return _topMargin + _localHeight + _bottomMargin };
		
		/**
		 * Position absolue par rapport au haut
		 */
		public function get top ():Number { return _top; }
		public function set top (value:Number):void
		{
			// Si la valeur est différente
			if (value != _top)
			{
				// Enregistrer
				_top = value;
				
				// Actualiser la position
				needReplace();
			}
		}
		
		/**
		 * Position absolue par rapport à la droite
		 */
		public function get right ():Number { return _right; }
		public function set right (value:Number):void
		{
			// Si la valeur est différente
			if (value != _right)
			{
				// Enregistrer
				_right = value;
				
				// Actualiser la position
				needReplace();
			}
		}
		
		/**
		 * Position absolue par rapport au bas
		 */
		public function get bottom ():Number { return _bottom; }
		public function set bottom (value:Number):void
		{
			// Si la valeur est différente
			if (value != _bottom)
			{
				// Enregistrer
				_bottom = value;
				
				// Actualiser la position
				needReplace();
			}
		}
		
		/**
		 * Position absolue par rapport à la gauche
		 */
		public function get left ():Number { return _left; }
		public function set left (value:Number):void
		{
			// Si la valeur est différente
			if (value != _left)
			{
				// Enregistrer
				_left = value;
				
				// Actualiser la position
				needReplace();
			}
		}
		
		/**
		 * Centrage horizontal du composant
		 */
		public function get horizontalCenter ():Number { return _horizontalCenter; }
		public function set horizontalCenter (value:Number):void
		{
			// Si la valeur est différente
			if (value != _horizontalCenter)
			{
				// Enregistrer
				_horizontalCenter = value;
				
				// Actualiser la position
				needReplace();
			}
		}
		
		/**
		 * Centrage vertical du composant
		 */
		public function get verticalCenter ():Number { return _verticalCenter; }
		public function set verticalCenter (value:Number):void
		{
			// Si la valeur est différente
			if (value != _verticalCenter)
			{
				// Enregistrer
				_verticalCenter = value;
				
				// Actualiser la position
				needReplace();
			}
		}
		
		/**
		 * Position horizontale
		 */
		override public function get x ():Number { return super.x - _leftMargin - _horizontalOffset; }
		override public function set x (value:Number):void
		{
			// Si la valeur est différente
			if (value != super.x + _leftMargin + _horizontalOffset)
			{
				// Appliquer
				super.x = value + _leftMargin + _horizontalOffset;
				
				// Actualiser la position
				replaced();
			}
		}
		
		/**
		 * Position verticale
		 */
		override public function get y ():Number { return super.y - _topMargin - _verticalOffset; }
		override public function set y (value:Number):void
		{
			// Si la valeur est différente
			if (value != super.y + _topMargin + _verticalOffset)
			{
				// Appliquer
				super.y = value + _topMargin + _verticalOffset;
				
				// Actualiser la position
				replaced();
			}
		}
		
		
		/**
		 * Signal appelé au redimensionnement
		 */
		public function get onResized ():Signal
		{
			return _onResized;
		}
		
		/**
		 * Le décalage de placement horizontal
		 */
		public function get horizontalOffset ():Number { return _horizontalOffset; }
		public function set horizontalOffset (value:Number):void 
		{
			// Si la valeur est différente
			if (value != _horizontalOffset)
			{
				// Enregistrer
				_horizontalOffset = value;
				
				// Actualiser la position
				needReplace();
			}
		}
		
		/**
		 * Le décalage de placement vertical
		 */
		public function get verticalOffset ():Number { return _verticalOffset; }
		public function set verticalOffset (value:Number):void 
		{
			// Si la valeur est différente
			if (value != _verticalOffset)
			{
				// Enregistrer
				_verticalOffset = value;
				
				// Actualiser la position
				needReplace();
			}
		}
		
		/**
		 * La marge du haut
		 */
		public function get topMargin ():int { return _topMargin; }
		public function set topMargin (value:int):void 
		{
			// Si la valeur est différente
			if (value != _topMargin)
			{
				// Enregistrer
				_topMargin = value;
				
				// Actualiser la position
				needReplace();
			}
		}
		
		/**
		 * La marge de droite
		 */
		public function get rightMargin ():int { return _rightMargin; }
		public function set rightMargin (value:int):void 
		{
			// Si la valeur est différente
			if (value != _rightMargin)
			{
				// Enregistrer
				_rightMargin = value;
				
				// Actualiser la position
				needReplace();
			}
		}
		
		/**
		 * La marge du bas
		 */
		public function get bottomMargin ():int { return _bottomMargin; }
		public function set bottomMargin (value:int):void 
		{
			// Si la valeur est différente
			if (value != _bottomMargin)
			{
				// Enregistrer
				_bottomMargin = value;
				
				// Actualiser la position
				needReplace();
			}
		}
		
		/**
		 * La marge de gauche
		 */
		public function get leftMargin ():int { return _leftMargin; }
		public function set leftMargin (value:int):void 
		{
			// Si la valeur est différente
			if (value != _leftMargin)
			{
				// Enregistrer
				_leftMargin = value;
				
				// Actualiser la position
				needReplace();
			}
		}
		
		/**
		 * Masquer le contenu
		 */
		public function get maskContent ():Boolean { return _maskContent; }
		public function set maskContent (value:Boolean):void
		{
			_maskContent = value;
			
			if (!_maskContent)
			{
				scrollRect = null;
			}
			else
			{
				needReplace(true);
			}
		}
		
		/**
		 * Si on autorise la suppression de cet élément
		 */
		public function get allowRemove ():Boolean { return _allowRemove; }
		public function set allowRemove (value:Boolean):void
		{
			_allowRemove = value;
		}
		
		
		/**
		 * Constructeur du composant avec gestion des dimensions
		 */
		public function ResizableComponent ()
		{
			
		}
		
		
		/**
		 * Initialisation du composant
		 */
		override protected function addedHandler (event:Event = null):void
		{
			// Si on a un parent redimensionnable
			if (parent != null && parent is ResizableComponent)
			{
				// Supprimer l'ancien si besoin
				if (_watchedParent != null)
					_watchedParent.onResized.remove(needReplace);
				
				// On le mémorise
				_watchedParent = (parent as ResizableComponent);
				
				// On écoute les changements de taille
				_watchedParent.onResized.add(needReplace);
				
				// Replacer
				needReplace();
			}
			
			// Relayer
			super.addedHandler(event);
		}
		
		/**
		 * Définir la taille du composant
		 * @param	pWidth : La largeur
		 * @param	pHeight : La hauteur
		 * @return this, méthode chaînable
		 */
		public function size (pWidth:Number, pHeight:Number):ResizableComponent
		{
			// Si c'est différent
			if (pWidth != _localWidth || pHeight != _localHeight)
			{
				// Enregistrer les nouvelles dimensions
				if (pWidth >= 0 || pWidth < 0)
					_localWidth = pWidth;
				
				if (pHeight >= 0 || pHeight < 0)
					_localHeight = pHeight;
				
				// Dispatcher
				_onResized.dispatch();
				
				// Signaler
				resized();
			}
			
			// Méthode chaînable
			return this;
		}
		
		/**
		 * Placer le composant. Permet de configurer toutes les variables de placement en redispatchant qu'un seul signal.
		 * @param	pTop : Le décallage depuis le haut
		 * @param	pRight : Le décallage depuis la droite
		 * @param	pBottom : Le décallage depuis le bas
		 * @param	pLeft : Le décallage depuis la gauche
		 * @return this, méthode chaînable
		 */
		public function place (pTop:Number = NaN, pRight:Number = NaN, pBottom:Number = NaN, pLeft:Number = NaN):ResizableComponent
		{
			// Tout enregistrer
			_top = pTop;
			_right = pRight;
			_bottom = pBottom;
			_left = pLeft;
			
			// Redispatcher
			needReplace();
			
			// Méthode chaînable
			return this;
		}
		
		/**
		 * Décaller le contenu du composant (la box ne bougera pas)
		 * @param	pHorizontalOffset : Le décallage horizontal (laisser NaN pour ne pas affecter)
		 * @param	pVerticalOffset : Le décallage vertical (laisser NaN pour ne pas affecter)
		 */
		public function offset (pHorizontalOffset:Number = NaN, pVerticalOffset:Number = NaN):ResizableComponent
		{
			// Si notre paramètres horizontal n'est pas null, on enregistre
			if (pHorizontalOffset >= 0 || pHorizontalOffset < 0)
				_horizontalOffset = pHorizontalOffset;
			
			// Si notre paramètres vertical n'est pas null, on enregistre
			if (pVerticalOffset >= 0 || pVerticalOffset < 0)
				_verticalOffset = pVerticalOffset;
			
			// Actualiser la position
			needReplace();
			
			// Méthode chaînable
			return this;
		}
		
		/**
		 * Les marges autour de la box de ce composant
		 * @param	pTopMargin : La marge du haut
		 * @param	pRightMargin : La marge de droite
		 * @param	pBottomMargin : La marge du bas
		 * @param	pLeftMargin : La marge de gauche
		 */
		public function margin (pTopMargin:int, pRightMargin:int, pBottomMargin:int, pLeftMargin:int):ResizableComponent
		{
			// Enregistrer les valeurs
			_topMargin = pTopMargin;
			_rightMargin = pRightMargin;
			_bottomMargin = pBottomMargin;
			_leftMargin = pLeftMargin;
			
			// Actualiser la position
			needReplace(true);
			
			// Méthode chaînable
			return this;
		}
		
		/**
		 * Centrer l'élément
		 * @param	pHorizontalCenter : Le décallage par rapport au centre horizontal
		 * @param	pVerticalCenter : Le décallage par rapport au centre vertical
		 * @return this, méthode chaînable
		 */
		public function center (pHorizontalCenter:Number = NaN, pVerticalCenter:Number = NaN):ResizableComponent
		{
			// Enregistrer
			_horizontalCenter = pHorizontalCenter;
			_verticalCenter = pVerticalCenter;
			
			// Redispatcher
			needReplace();
			
			// Méthode chaînable
			return this;
		}
		
		/**
		 * Spécifier le parent de ce composant
		 * @param	pParent : Le parent auquel ajouter ce composant
		 * @param	pName : Le nom de ce composant
		 * @param	pAt : A quel niveau (-1 pour au dessus)
		 * @return this, méthode chaînable
		 */
		public function into (pParent:DisplayObjectContainer, pName:String = "", pAt:int = -1):ResizableComponent
		{
			// Définir le nom si besoin
			if (pName != "")
				name = pName;
			
			// Si le parent est null
			if (pParent == null && parent != null)
			{
				// On supprime
				parent.removeChild(this);
			}
			else
			{
				// Ajouter au parent au bon étage
				if (pAt == -1)
					pParent.addChild(this);
				else
					pParent.addChildAt(this, pAt);
			}
			
			// Méthode chaînable
			return this;
		}
		
		/**
		 * Le composant a besoin d'être replacé
		 */
		protected function needReplace (pNeedResized:Boolean = false):void
		{
			// Si on a un parent
			if (_watchedParent != null)
			{
				// Récupérer les dimensions actuelle
				var oldWidth		:Number = _localWidth;
				var oldHeight		:Number = _localHeight;
				
				// Récupérer la position actuelle
				var oldXPosition	:Number = super.x;
				var oldYPosition	:Number = super.y;
				
				// Centrer horizontalement ou placer
				if (_horizontalCenter <= 0 || _horizontalCenter > 0)
				{
					// Centrer
					if (_localWidth <= 0 || _localWidth > 0)
						super.x = _watchedParent.width / 2 - _localWidth / 2 + _horizontalCenter + _horizontalOffset;
					else
						super.x = _watchedParent.width / 2 - _horizontalCenter + _horizontalOffset;
				}
				
				// A gauche et à droite
				else if ((_left <= 0 || _left > 0) && (_right <= 0 || _right > 0))
				{
					// Largeur fluide
					_localWidth = Math.max(0, _watchedParent.width - _left - _right - _leftMargin - _rightMargin);
					
					// Actualiser la position
					super.x = _left + _horizontalOffset + _leftMargin;
				}
				
				// Alignement sur la droite
				else if (!(_left <= 0 || _left > 0) && (_right <= 0 || _right > 0))
				{
					// Placer à droite
					super.x = _watchedParent.width - _localWidth - _right - _rightMargin + _horizontalOffset;
				}
				
				// Alignement sur la gauche
				else if (_left <= 0 || _left > 0)
				{
					// Placer à gauche
					super.x = _left + _horizontalOffset + _leftMargin;
				}
				
				// Placement normal sur les marges
				else if (_horizontalOffset != 0 || _leftMargin != 0)
				{
					super.x = _horizontalOffset + _leftMargin;
				}
				
				
				// Centrer verticalement ou placer
				if (_verticalCenter <= 0 || _verticalCenter > 0)
				{
					// Centrer
					if (_localHeight <= 0 || _localHeight > 0)
						super.y = _watchedParent.height / 2 - _localHeight / 2 + _verticalCenter + _verticalOffset;
					else
						super.y = _watchedParent.height / 2 + _verticalCenter + _verticalOffset;
				}
				
				// Alignement en haut et en bas
				else if ((_top <= 0 || _top > 0) && (_bottom <= 0 || _bottom > 0))
				{
					// Hauteur fluide
					_localHeight = Math.max(0, _watchedParent.height - _top - _bottom - _topMargin - _bottomMargin);
					
					// Placer
					super.y = _top + _verticalOffset + _topMargin;
				}
				
				// Alignement en bas
				else if (!(_top <= 0 || _top > 0) && (_bottom <= 0 || _bottom > 0))
				{
					// Placer en bas
					super.y = _watchedParent.height - _localHeight - _bottom - _bottomMargin + _verticalOffset;
				}
				
				// Alignement en haut
				else if (_top <= 0 || _top > 0)
				{
					// Placer en haut
					super.y = _top + _verticalOffset + _topMargin;
				}
				
				// Placement normal sur les marges
				else if (_verticalOffset != 0 || _topMargin != 0)
				{
					super.y = _verticalOffset + _topMargin;
				}
				
				
				// Si le composant a bougé
				if (super.x != oldXPosition || super.y != oldYPosition)
				{
					// Signaler
					replaced();
				}
				
				// Si le composant a été redimensionné
				if (_localWidth != oldWidth || _localHeight != oldHeight)
				{
					pNeedResized = true;
				}
			}
			
			// Si on a touché aux dimensions du composant
			if (pNeedResized)
			{
				// Dispatcher
				_onResized.dispatch();
				
				// Actualiser le masque
				if (_maskContent)
					scrollRect = new Rectangle(0, 0, _localWidth, _localHeight);
				
				// Signaler
				resized();
			}
		}
		
		/**
		 * Si on doit placer ce composant en bitmap
		 */
		public function cab (pCacheAsBitmapEnabled:Boolean, pMatrix:Matrix = null):ResizableComponent
		{
			// Appliquer le cacheAsBitmap
			cacheAsBitmap = pCacheAsBitmapEnabled;
			cacheAsBitmapMatrix = pMatrix;
			
			// Méthode chaînable
			return this;
		}
		
		/**
		 * Le composant est redimentionné
		 */
		protected function resized ():void
		{
			
		}
		
		/**
		 * Le composant est replacé
		 */
		protected function replaced ():void
		{
			
		}
		
		/**
		 * Si le composant est interactif
		 */
		public function interactive (pValue:Boolean):ResizableComponent
		{
			// Appliquer
			mouseEnabled = pValue;
			mouseChildren = pValue;
			
			// Méthode chaînable
			return this;
		}
		
		/**
		 * Destruction du composant
		 */
		override protected function removedHandler (event:Event):void
		{
			// Relayer
			super.removedHandler(event);
			
			// Supprimer tous les listeners
			_onResized.removeAll();
			
			// On n'écoute les changements de taille
			if (_watchedParent != null)
			{
				_watchedParent.onResized.remove(needReplace);
				_watchedParent = null;
			}
		}
	}
}